Skip to content

Add fileaccess harness context provider with shared-folder file tools rooted at a directory - #643

Open
PratikDhanave (PratikDhanave) wants to merge 5 commits into
microsoft:mainfrom
PratikDhanaveFork:fileaccess-harness-provider
Open

Add fileaccess harness context provider with shared-folder file tools rooted at a directory#643
PratikDhanave (PratikDhanave) wants to merge 5 commits into
microsoft:mainfrom
PratikDhanaveFork:fileaccess-harness-provider

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

Adds a new self-contained agent/harness/fileaccess package, wired like the sibling agent/harness/todo provider. New(*Options) returns a Provider backed by agent.NewContextProvider, and its Provide hook injects file tools plus instructions on each invocation.

The provider exposes the same six tools as the .NET FileAccessProvider:

  • file_access_read_file
  • file_access_save_file
  • file_access_list_files
  • file_access_list_subdirectories
  • file_access_search_files
  • file_access_delete_file

All operations go through a local-filesystem store rooted at a caller-granted Options.RootDir (not session state). Options.ReadOnly omits the save/delete tools, matching the .NET read-only shipping mode.

Why

The Go harness tree (agent/harness/) had agentmode, loop, todo, toolapproval, and toolautocall, but no file-access counterpart, while the .NET SDK ships FileAccessProvider with exactly this tool set (file_access_read_file / save_file / list_files / list_subdirectories / search_files / delete_file) and a read-only mode. This closes that cross-SDK parity gap so Go agents can be granted a scoped shared folder.

Safety

Every path is interpreted relative to the root. The store resolves paths with filepath.Clean(filepath.Join(root, rel)) and rejects anything that is absolute or escapes the root via a prefix check, so ../outside.txt and absolute paths are refused before touching the filesystem.

Tests

fileaccess_test.go is black-box (package fileaccess_test) and reuses the same harness style as todo_test.go (agenttest.CreateSession, driving tools through the exported Invoking API). It covers: default tool set + instructions present; ReadOnly omitting save/delete; save->read round trip; list_files direct-children-only vs list_subdirectories; search_files regex across nested files; delete; and path-escape rejection (../outside.txt, absolute path, escaping write not creating a file).

go build ./..., go vet ./agent/harness/fileaccess/..., and go test ./agent/harness/fileaccess/... all pass.

Open design questions

  • Scope: The store is a minimal local-filesystem implementation held inside the package. Should it instead be an exported interface so callers can back it with other stores (blob, in-memory), mirroring any abstraction on the .NET side?
  • API shape: Tool argument/return shapes (relative-path strings, search_files returning slash-separated relative paths matched against file contents) are chosen for parity; happy to align field names/semantics exactly with the .NET tool schemas if they differ.
  • Follow-ups: The .NET provider ships read-only mode as an auto-approval rule via the tool-approval harness. This PR keeps the package self-contained (ReadOnly simply omits the mutating tools); wiring an explicit auto-approval rule through toolapproval could be a follow-up.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the public-api-change Pull Request changes public APIs label Jul 24, 2026
Introduce a self-contained agent/harness/fileaccess package that mirrors the
.NET FileAccessProvider. It registers a context provider that injects file
tools scoped to a caller-granted root directory: file_access_read_file,
file_access_save_file, file_access_list_files, file_access_list_subdirectories,
file_access_search_files, and file_access_delete_file.

The root comes from Options.RootDir (not session state). A local-filesystem
store constrains every operation to the root, rejecting absolute paths and
".." traversal via filepath.Clean plus a prefix check. Options.ReadOnly omits
the save and delete tools to match the .NET read-only shipping mode.
@github-actions

This comment has been minimized.

@PratikDhanave
PratikDhanave (PratikDhanave) marked this pull request as ready for review August 4, 2026 06:06
@PratikDhanave
PratikDhanave (PratikDhanave) requested a review from a team as a code owner August 4, 2026 06:06
Copilot AI lite review requested due to automatic review settings August 4, 2026 06:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Go harness context provider (agent/harness/fileaccess) that injects shared-folder file tools (read/save/list/search/delete) into agent invocations, intended to mirror the .NET FileAccessProvider and support a read-only mode.

Changes:

  • Introduces fileaccess.Provider with tool injection + default/read-only instructions and a local filesystem-backed store rooted at Options.RootDir.
  • Implements six file tools (file_access_*) with path resolution intended to constrain access to the configured root directory, plus read-only mode by omitting mutating tools.
  • Adds black-box tests validating tool exposure, read-only behavior, round-trip save/read, list/search semantics, delete, and basic ../absolute-path escape rejection.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
agent/harness/fileaccess/fileaccess.go New file-access provider and local filesystem store for shared-folder tool operations.
agent/harness/fileaccess/fileaccess_test.go New black-box tests covering tool presence/behavior, basic path escape rejection, and core operations.
Suppressed comments (1)

agent/harness/fileaccess/fileaccess.go:283

  • Path containment checks do not account for symlinks inside the root. For example, if the shared folder contains a symlink directory like "link" -> "/tmp", calling save_file with path "link/outside.txt" will pass resolve() (it stays under root textually) but os.MkdirAll/os.WriteFile will follow the symlink and write outside the root. The same issue applies to read/delete/search for symlink files.
func (s *store) SaveFile(rel, content string) error {
	full, err := s.resolve(rel)
	if err != nil {
		return err
	}

Comment on lines +10 to +13
// All operations are constrained to the configured root directory. Paths are
// resolved relative to the root and any attempt to escape it (via "..", an
// absolute path, or symlink-style traversal in the supplied name) is rejected.
//
Comment on lines +258 to +262
full := filepath.Clean(filepath.Join(s.root, rel))
if full != s.root && !strings.HasPrefix(full, s.root+string(os.PathSeparator)) {
return "", fmt.Errorf("path %q escapes the shared folder root", rel)
}
return full, nil
@github-actions

This comment has been minimized.

@gdams

Copy link
Copy Markdown
Member

PratikDhanave (@PratikDhanave) can you resolve the parity gaps?

@github-actions github-actions Bot added area:agent Changes files in the agent area size:xlarge More than 300 changed lines or 10 files pending-auto-risk Automatic risk classification is in progress labels Aug 20, 2026
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by Go API Consistency Review Agent · sonnet46 · 39.6 AIC · ⌖ 5 AIC · ⊞ 6K

// Package fileaccess provides a context provider that gives agents file tools
// for reading and writing files inside a single caller-granted directory (a
// "shared folder"). It mirrors the .NET FileAccessProvider, exposing the same
// set of tools: file_access_read_file, file_access_save_file,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity issue: Tool names diverge from .NET FileAccessProvider

The Go package doc comments (and the actual tool registrations) use different tool names than the .NET FileAccessProvider ships:

Go name .NET name
file_access_read_file file_access_read
file_access_save_file file_access_write
file_access_list_files file_access_ls
file_access_list_subdirectories (no separate tool; .NET's file_access_ls returns both files and dirs)
file_access_search_files file_access_grep
file_access_delete_file file_access_delete

Ref: dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.csWriteToolName, ReadFileToolName, LsToolName, GrepToolName, DeleteFileToolName.

Using different tool names means prompts, instructions, and tests written for one SDK will not transfer to the other. Please align with the upstream .NET names (or document a justified divergence).

// file_access_delete_file tools so the agent can only read.
ReadOnly bool

// Instructions overrides the default instructions provided to the agent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity issue: Missing file_access_replace and file_access_replace_lines tools

The .NET FileAccessProvider ships two additional tools not present in this Go package:

  • file_access_replace — replaces occurrences of a substring within a file (avoids full rewrites)
  • file_access_replace_lines — replaces whole lines within a file

These are write-mode tools omitted when DisableWriteTools is set.

Ref: dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.csReplaceToolName, ReplaceLinesToolName.

If these are intentionally deferred, please note that in the PR description and/or add a TODO; otherwise the Go provider is functionally incomplete relative to .NET.

- Use file_access_list_files to list the files directly inside a folder.
- Use file_access_list_subdirectories to list the sub-folders directly inside a folder.
- Use file_access_search_files to find files whose contents match a regular expression.`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity issue: Options diverges from .NET FileAccessProviderOptions

The Go Options struct uses ReadOnly bool while the .NET equivalent uses DisableWriteTools bool. These are semantically equivalent, but the name divergence means documentation and cross-SDK guidance will be inconsistent. Please use DisableWriteTools (or add it as an alias) to match the upstream name.

Also, the .NET options expose two additional fields that Go omits:

  • DisableReadOnlyToolApproval bool — disables the approval requirement for read-only tools
  • DisableWriteToolApproval bool — disables the approval requirement for write tools

And the upstream type is marked [Experimental]; Go may want an equivalent //go:build constraint or a package-level note.

Ref: dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProviderOptions.cs.

store *store
readOnly bool
instructions string
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity issue: Tool-approval integration is absent

The .NET FileAccessProvider ships two static auto-approval rules as first-class public API:

  • FileAccessProvider.ReadOnlyToolsAutoApprovalRule — auto-approves file_access_read, file_access_ls, file_access_grep
  • FileAccessProvider.AllToolsAutoApprovalRule — auto-approves all seven tools

These are intended to be registered with the ToolApprovalAgent harness, and the PR description acknowledges the .NET read-only mode uses this pattern. Go's sibling toolapproval harness package presumably supports the same hook. Please either:

  1. Expose equivalent exported AutoApprovalRule values in this package so callers can wire them into the approval harness, mirroring the .NET public contract, or
  2. Document explicitly why this is deferred and what callers should do instead.

Ref: dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.csReadOnlyToolsAutoApprovalRule, AllToolsAutoApprovalRule.

@github-actions github-actions Bot added failed-auto-risk Automatic risk classification was inconclusive or failed and removed pending-auto-risk Automatic risk classification is in progress labels Aug 20, 2026
@github-actions github-actions Bot added pending-auto-risk Automatic risk classification is in progress risk:medium Contained production impact requiring normal review depth and removed failed-auto-risk Automatic risk classification was inconclusive or failed pending-auto-risk Automatic risk classification is in progress labels Aug 22, 2026
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by Go API Consistency Review Agent · sonnet46 · 46.8 AIC · ⌖ 5.64 AIC · ⊞ 6K

Pattern string `json:"pattern"`
// Path is the folder to search under, relative to the root. Empty means the root.
Path string `json:"path,omitempty"`
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity Issue: Tool names diverge from .NET and Python implementations

The Go PR uses different tool names from the upstream .NET and Python implementations. Both .NET (FileAccessProvider.cs) and Python (_file_access.py) use a consistent, compact naming scheme:

Upstream (.NET + Python) Go (this PR)
file_access_read file_access_read_file
file_access_write file_access_save_file
file_access_delete file_access_delete_file
file_access_ls file_access_list_files
file_access_list_subdirectories (Go-only)
file_access_grep file_access_search_files
file_access_replace (missing)
file_access_replace_lines (missing)

Tool names are part of the user-visible agent contract — prompts, documentation, and agent configurations referencing them by string will behave differently across SDKs. The PR description claims parity with .NET but the tool names diverge.

Recommendation: Align names with upstream: file_access_read, file_access_write, file_access_delete, file_access_ls, file_access_grep. The separate file_access_list_subdirectories tool is Go-only; upstream .NET/Python handle directory listing through a single file_access_ls that returns both files and directories. Consider aligning or explicitly documenting the divergence.

// used.
RootDir string

// ReadOnly, when true, omits the file_access_save_file and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity Issue: Tool approval gating is absent — upstream requires explicit approval by default

Both the .NET (FileAccessProvider.cs) and Python (_file_access.py) implementations register every file-access tool with an approval-required mode by default:

  • .NET (FileAccessProviderOptions): all tools are exposed as ApprovalRequiredAIFunction. Approval can be disabled per group via DisableReadOnlyToolApproval and DisableWriteToolApproval (both default false). Static auto-approval rules (ReadOnlyToolsAutoApprovalRule, AllToolsAutoApprovalRule) are provided for unattended use.
  • Python: same design — all tools default to approval_mode="always_require". The options disable_readonly_tool_approval and disable_write_tool_approval (both default False) opt out, and static read_only_tools_auto_approval_rule / all_tools_auto_approval_rule class methods support unattended use.

The Go Options struct has no DisableReadOnlyToolApproval, DisableWriteToolApproval, or equivalent options. Tools run unconditionally without any approval gating.

The Go harness already has a toolapproval package, so the plumbing exists. This PR should either:

  1. Add DisableReadOnlyToolApproval / DisableWriteToolApproval fields to Options and wire them to the toolapproval harness (mirroring upstream), or
  2. Explicitly document the intentional divergence and explain why the Go SDK takes a different default posture.

Without this, Go agents running file-access tools will execute all file operations unconditionally, which contradicts the upstream safety model.

@github-actions github-actions Bot added pending-auto-risk Automatic risk classification is in progress and removed risk:medium Contained production impact requiring normal review depth labels Aug 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Cross-SDK Parity Review

Scope: public API, user-visible behavior
Changed Go contract: New exported package agent/harness/fileaccessOptions, Provider, New(*Options), SourceID, and six file tools
Upstream evidence reviewed:

  • dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.csFileAccessProvider, WriteToolName, ReadFileToolName, DeleteFileToolName, LsToolName, GrepToolName, ReplaceToolName, ReplaceLinesToolName
  • dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProviderOptions.csDisableWriteTools, DisableReadOnlyToolApproval, DisableWriteToolApproval
  • dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/FileAccess/FileAccessProviderTests.cs — 7-tool count assertions, approval-required assertions
    Result: findings reported — three material parity gaps identified below

Finding 1 — Tool names diverge from .NET

The Go package uses different tool names than the .NET FileAccessProvider:

Concept .NET tool name Go tool name
Write/create file file_access_write file_access_save_file
Read file file_access_read file_access_read_file
Delete file file_access_delete file_access_delete_file
List files file_access_ls file_access_list_files
List subdirs (part of ls) file_access_list_subdirectories
Search file_access_grep file_access_search_files

Tool names are part of the user-visible protocol: they appear in LLM prompt schemas and in chat histories. Divergent names mean a session transcript from Go and one from .NET would reference different function names, breaking cross-SDK interpretability.

Upstream reference: dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.cspublic const string WriteToolName = "file_access_write", ReadFileToolName, LsToolName, GrepToolName, DeleteFileToolName.

Suggested fix: Align Go tool names with the .NET constants (file_access_write, file_access_read, file_access_delete, file_access_ls, file_access_grep). If the Go split of ls into two tools (files vs. subdirectories) is intentional, document the divergence explicitly.


Finding 2 — Two .NET tools are missing: file_access_replace and file_access_replace_lines

The .NET FileAccessProvider in full (non-ReadOnly) mode exposes 7 tools. Go exposes 6 and is missing:

  • file_access_replace — replace occurrences of a substring within a file without a full read-modify-write cycle
  • file_access_replace_lines — replace whole lines within a file

These are write-mode tools suppressed by DisableWriteTools/ReadOnly, so they are squarely in scope for a Go port targeting .NET parity.

Upstream reference: FileAccessProvider.cspublic const string ReplaceToolName = "file_access_replace" and ReplaceLinesToolName = "file_access_replace_lines"; FileAccessProviderTests.cs asserts Assert.Equal(7, tools.Count()).

Suggested fix: Add file_access_replace and file_access_replace_lines tools, or explicitly document that this PR intentionally ships a subset and tracks the remainder as a follow-up.


Finding 3 — Tool-approval model is absent; Go fires tools unconditionally

In .NET, every FileAccessProvider tool is wrapped as an ApprovalRequiredAIFunction by default. Callers opt out via DisableReadOnlyToolApproval and DisableWriteToolApproval options, or grant auto-approval via ReadOnlyToolsAutoApprovalRule / AllToolsAutoApprovalRule on the ToolApprovalAgent.

Go exposes plain FuncTool values with no approval wrapper. Any agent using this provider in full (non-ReadOnly) mode will execute file writes and deletes without any approval prompt — a material default-behavior difference from .NET.

Upstream reference: FileAccessProviderOptions.csDisableReadOnlyToolApproval, DisableWriteToolApproval; FileAccessProviderTests.csProvideAIContextAsync_AllToolsRequireApprovalAsync asserts every tool is ApprovalRequiredAIFunction.

Suggested fix: Either wire Go tools through the existing toolapproval harness (with equivalent DisableReadOnlyToolApproval / DisableWriteToolApproval option fields), or document the explicit divergence. The PR description already flags this as a potential follow-up; at minimum, the Options struct should include a comment so callers understand the difference.


Minor note — AgentFileStore abstraction

The .NET provider accepts an AgentFileStore interface, enabling pluggable backends (in-memory, local FS, blob storage). Go hardwires local FS. The PR description acknowledges this as an open design question — no action required now, but worth tracking.

Generated by Go API Consistency Review Agent · sonnet46 · 29.1 AIC · ⌖ 5.44 AIC · ⊞ 6.4K ·

@github-actions github-actions Bot added risk:medium Contained production impact requiring normal review depth and removed pending-auto-risk Automatic risk classification is in progress labels Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent Changes files in the agent area public-api-change Pull Request changes public APIs risk:medium Contained production impact requiring normal review depth size:xlarge More than 300 changed lines or 10 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants